package cn.pink.core.gen.observer;

import cn.pink.core.gen.GenBase;
import cn.pink.core.gen.GenUtils;
import cn.pink.core.support.ClassFinder;
import cn.pink.core.support.SysException;
import cn.pink.core.support.Utils;
import cn.pink.core.support.observer.EventObserver;
import cn.pink.core.support.observer.MsgObserver;
import cn.pink.core.support.observer.Subject;
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * 观察者代码自动生成
 * @Author: pink
 * @Date: 2022/6/30 16:08
 */
public class GenObserverInit extends GenBase {
    private String genClassName;
    private String ftlFileName;
    private Class<? extends Annotation> annoClass;

    public GenObserverInit(String packageName, String targetDir, String genClassName,
                           String ftlFileName, Class<? extends Annotation> annoClass) throws Exception {
        super(packageName, targetDir);
        this.genClassName = genClassName;
        this.ftlFileName = ftlFileName;
        this.annoClass = annoClass;
        this.init();
    }

    /**
     * 初始化class信息，是否能生成等字段
     * @throws Exception
     */
    private void init() throws Exception {
        /**  默认是true */
        this.canGen = true;

        // 遍历所有类信息, 获取模板信息, 判断是否出错, 出错则初始化异常不能生成
        Map<Class<?>, List<Method>> classes = GenUtils.findMethodNeedToGen(this.packageName, null, annoClass);
        for (Map.Entry<Class<?>, List<Method>> entry : classes.entrySet()) {
            Class<?> clazz = entry.getKey();
            List<Method> ms = entry.getValue();
            try {
                this.rootMaps.add(getRootMap(clazz, ms));
            } catch (Exception e) {
                // 如果获取模板内容异常，表示不能生成
                /**  不能生成 */
                this.canGen = false;
                System.err.println("文件存在错误，不继续进行Observer生成了，错误如下：");
                e.printStackTrace();
            }
        }

    }

    /**
     * 生成Proxy类的核心方法
     * @param rootMap
     * @throws Exception
     */

    @Override
    protected void genFileHandler(Map<String, Object> rootMap) throws Exception {

    }


    @Override
    protected void genGlobalFileHandler(Map<String, Object> rootMaps) throws Exception {
        rootMaps.put("rootClassName", genClassName);

        String targetFileDir = this.targetDir + ClassFinder.packageToPath(this.packageName) + "/";
        Utils.freeMarker(TEMP_DIR, ftlFileName, rootMaps, targetFileDir, genClassName + ".java");
    }

    /**
     * 根据Class及其methods获取填充模板内容
     * @param clazz
     * @param methods
     * @return
     * @throws Exception
     */
    private Map<String, Object> getRootMap(Class<?> clazz, List<Method> methods) throws Exception {
        // 获取实体类名,表名,包名
        String packageName = clazz.getPackage().getName();
        String className = clazz.getSimpleName();


        // 填充Map
        Map<String, Object> rootMap = new HashMap<>();
        List<Map<String, Object>> methodInfos = new ArrayList<>();
        rootMap.put("packageName", packageName);
        rootMap.put("className", className);
        rootMap.put("methods", methodInfos);

        // 遍历methods，获取方法名，方法参数等信息
        for (Method m : methods) {
            // 模板所需数据
            String name = m.getName();
            String callerStr = packageName + "." + className + ":" + name;
            String paramsCall = "";
            String params = "";
            String functionTypes = "";
            boolean hasException = m.getExceptionTypes().length > 0;

            Map<String, Object> method = new LinkedHashMap<>();
            Map<String, Object> paramInfo = new LinkedHashMap<>();

            // 获得keys相关信息
            Set<String> keys = getListenerKey(m);
            method.put("keys", keys);

            // 获取方法形参的类型，参数名
            Parameter[] paramList = m.getParameters();
            for (int i = 0; i < paramList.length; i++) {
                /* 这边为了只保留class.getSimpleName()得到的类名，而不是类名$内部类名，用正则替换了$以前的字符为空
                 * 如com.google.protobuf.Message.Builder，Builder是Message的内部类，分别是如下结果
                 * ctclass.getSimpleName() -> Message$Builder
                 * ctclass.getSimpleName().replaceAll("^.+\\$", "") -> Builder
                 * 非内部类不影响结果
                 */
                /**  参数类型 */
                String ptype = paramList[i].getType().getCanonicalName();//.replaceAll("^.+\\$", "");
                /**  参数名 */
                String pname = paramList[i].getName();
                paramInfo.put(pname, ptype);
            }

            // 取出具体模板所需方法形参类型，形参名
            int j = 0;
            String callerStrTmp = "";
            for (Map.Entry<String, Object> info: paramInfo.entrySet()) {
                String pname = info.getKey();
                String ptype = (String) info.getValue();

                if (j > 0) {
                    params += ", ";
                    callerStrTmp += ", ";
                    paramsCall += ", ";
                    functionTypes += ", ";
                }

                callerStrTmp += ptype;
                paramsCall += pname;
                params += ptype.replaceAll("\\[\\]", "...") + " " + pname;
                functionTypes += GenUtils.primitiveTowrapper(ptype);

                j ++;
            }

            callerStr += "(" + callerStrTmp + ")";

            if (StringUtils.isNotBlank(functionTypes)) {
                functionTypes = "<" + functionTypes + ">";
            }

            method.put("name", name);
            method.put("params", params);
            method.put("hasException", hasException);
            method.put("callerStr", callerStr);
            method.put("paramsCall", paramsCall);
            method.put("functionTypes", functionTypes);
            method.put("paramsSize", j);

            //生成方法名对应的Enum常量
            String enumCall = callerStr.replace("()", "").replace("[]", "s").replaceAll("[.:(,]", "_").replaceAll("[ )]", "").toUpperCase();
            method.put("enumCall", enumCall);
            method.put("enumCallHashCode", String.valueOf(enumCall.hashCode())) ;

            methodInfos.add(method);
        }

        return rootMap;
    }

    /**
     * 获取被监听事件的关键字
     * @param method
     * @return
     * @throws Exception
     */
    private Set<String> getListenerKey(Method method) throws Exception {
        Annotation anno = (Annotation)method.getAnnotation(annoClass);
        Method mKey = annoClass.getMethod("value");
        Object oKey = mKey.invoke(anno);
        Object[] vKey;
        if (oKey instanceof int[]) {
            int[] keys = (int[])oKey;
            vKey = new Object[keys.length];
            for (int i = 0; i < keys.length; ++i) {
                vKey[i] = keys[i];
            }
        } else {
            vKey = (Object[])oKey;
        }

        // 获取注解设置的主事件
        String[] vSubStr = {};
        int[] vSubInt = {};
        long[] vSubLong = {};
        for (Method m : annoClass.getMethods()) {
            String mName = m.getName();
            if ("subStr".equals(mName)) {
                vSubStr = (String[])m.invoke(anno);
            } else if ("subInt".equals(mName)) {
                vSubInt = (int[])m.invoke(anno);
            } else if ("subLong".equals(mName)) {
                vSubLong = (long[])m.invoke(anno);
            }
        }

        // 多个子事件key，只容许设置一个，这里检查下
        int vSubCount = 0;
        if (vSubStr.length > 0) {
            vSubCount++;
        }
        if (vSubInt.length > 0) {
            vSubCount++;
        }
        if (vSubLong.length > 0) {
            vSubCount++;
        }

        // 设置监听了多个不同类型的子事件
        if (vSubCount > 1) {
            throw new SysException("Observer监听参数设置错误，不允许同时设置多种不同参数类型的子事件："
                    + "method={}, anno={}, subStr={}, subInt={}, subLong={}",
                    method, anno, vSubStr, vSubInt, vSubLong);
        }

        // 获取子类型设置
        String[] vSubKey = getSubKeysFromValue(vSubStr, vSubInt, vSubLong);

        // 监听关键字
        Set<String> results = new HashSet<>();

        // 需要监听的事件关键字
        for (Object k : vKey) {
            // 如果主事件和子事件都有多个，那么会出现乘积，暂时先允许
            for (String sk : vSubKey) {
                String smk = Subject.createObserverKey(k, sk);
                results.add(smk);
            }

            // 如果没有设置子事件，那么就生成主事件
            if (vSubKey.length == 0) {
                String smk = Subject.createObserverKey(k, null);
                results.add(smk);
            }
        }

        return results;
    }

    private static String[] getSubKeysFromValue(String[] vSubStr, int[] vSubInt, long[] vSubLong) {
        String[] vSubKey = {};

        if (vSubStr.length > 0) {
            vSubKey = new String[vSubStr.length];
            for (int i = 0; i < vSubStr.length; ++i) {
                vSubKey[i] = vSubStr[i];
            }
        } else if (vSubInt.length > 0) {
            vSubKey = new String[vSubInt.length];
            for (int i = 0; i < vSubInt.length; ++i) {
                vSubKey[i] = String.valueOf(vSubInt[i]);
            }
        } else if (vSubLong.length > 0) {
            vSubKey = new String[vSubLong.length];
            for (int i = 0; i < vSubLong.length; ++i) {
                vSubKey[i] = String.valueOf(vSubLong[i]);
            }
        }

        return vSubKey;
    }

    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.out.println("usage: packageName, targetDir");
            return;
        }

        //设置log4j2配置文件所需的环境变量，作用是gen的时候
        //不会报配置没找到的错误，同时有gen.log的日志记录
        System.setProperty("logFileName", "GenObserverInit");

        String packageName = args[0];
        String targetDir = System.getProperty("user.dir") + args[1];

        GenBase genMsgReceiverInit = new GenObserverInit(packageName, targetDir, "MsgObserverInit", "ObserverInit.ftl", MsgObserver.class);
        GenBase genListenerInit = new GenObserverInit(packageName, targetDir, "EventObserverInit", "ObserverInit.ftl", EventObserver.class);

        if (!genMsgReceiverInit.isCanGen()) {
            System.err.println("不能生成MsgReceiverInit，请检查重试。。");
            return ;
        }

        if (!genListenerInit.isCanGen()) {
            System.err.println("不能生成ListenerInit，请检查重试。。");
            return ;
        }

        genMsgReceiverInit.genFiles();
        genListenerInit.genFiles();
    }
}
